<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>创建对象</title>
    <style>
        dt {
            font-weight: bold
        }
    </style>
</head>

<body>
    <div class="part">
        <h1>6.2.1 工厂模式</h1>
        <section>
            <p>该模式是软件工程领域一种广为人知的设计模式，该模式抽象了创建具体对象的过程。</p>
            <p>考虑到在ES中无法创建类，开发人员就发明了一种函数，用函数来封装一特定接口创建对象的细节。</p>
            <ul>
                <li>
                    优点：工厂模式解决了创建多个相似对象的问题。
                </li>
                <li>缺点：没有解决对象识别问题。</li>
            </ul>
            <div class="actions">
                <button onclick="handleCreateObjectFactoryPattern()">用工厂模式创建对象</button>
            </div>
        </section>
    </div>
    <div class="part">
        <h1>6.2.2 构造函数模式</h1>
        <section>
            <p>ES中构造函数可以用来创建特定类型的对象。像Object和Array这样的原生构造函数，在运行时会自动出现在执行环境中。</p>
            <p>此外，也可以创建自定义的构造函数，从而定义自定义对象的属性跟方法。</p>
            <dl>
                <dt>构造函数模式跟工厂模式不同之处</dt>
                <dd>没有显示的创建对象</dd>
                <dd>直接将属性和方法赋给this</dd>
                <dd>没有return语句</dd>
                <dd>按照惯例，构造函数始终都应该以一个大写字母开头</dd>
            </dl>
            <dl>
                <dt>要创建Person的新实例，必须使用new操作符。以这种方式调用构造函数实际上要经历以下4个步骤。</dt>
                <dd>1)创建一个新对象</dd>
                <dd>2)将构造函数的作用域赋给新对象（因此，this就指向了这个新对象）</dd>
                <dd>3)指向构造函数中的代码（为这个新对象添加属性）</dd>
                <dd>4)返回新对象</dd>
            </dl>
            <dl>
                <dt>其他说明</dt>
                <dd>实例出来的对象都有一个<strong>constructor</strong>（构造函数）的属性，该属性指向构造函数。</dd>
                <dd>提到检测对象类型，还是<strong>instanceof</strong>操作符更可靠一些。我们创建的对象既是Object实例，也是Person实例。</dd>
                <dd>创建自定义构造函数意味着将来可以将它的实例标识为一种特定的类型；这正是构造函数模式胜过工厂模式的地方。</dd>
            </dl>
            <dl>
                <dt>注意点</dt>
                <dd>将构造函数当作函数。构造函数与其他函数的唯一区别，就在于调用他们的方式不同。</dd>
                <dd>构造函数也是函数，不存在定义构造函数的特殊语法。</dd>
                <dd>任何函数，如果用new操作符来调用，那么它就作为构造函数。而任何函数如果不用new操作符调用，那么它跟普通函数没啥两样。</dd>
            </dl>
            <dl>
                <dt>优缺点</dt>
                <dd>优点：能识别具体实例是从何而来</dd>
                <dd>缺点：每个方法都要在每个实例上创建一遍。也可以在全局作用域里定义函数，具体sayName指向该函数，但是全局作用域的函数只有某个对象调用不太合适。里另外如果对象需要定义好多函数，那么需要定义非常多的全局函数，完全没有封装性可言。
                </dd>
            </dl>
            <div class="actions">
                <button onclick="handleCreateObjectConstructorPattern()">用构造函数模式创建对象</button>
            </div>
        </section>
    </div>
    <div class="part">
        <h1>6.2.3 原型模式</h1>
        <section>
            <dl>
                <dt>概述</dt>
                <dd>我们创建的每一个函数都有一个prototype（原型）属性，这个属性是一个指针，指向一个对象，而这个对象的用途是包含可以由特定类型所有实例共享的属性跟方法。</dd>
                <dd>如果按照字面理解，那么prototype就是通过调用构造函数而创建的那个<b>对象实例</b>的<b>原型对象</b>。</dd>
                <dd>当为对象实例添加一个属性时，就会屏蔽原型对象里保存的同名属性；换句话说，添加的这个属性会阻止我们去访问原型中的那个同名属性，但是不会修改原型属性。</dd>
                <dd>即使这个属性设为null，也只会在实例里设置这个属性，而不会恢复其指向原型的连接。</dd>
                <dd>不过，使用delete可以完全删除实例属性，从而让我们重新访问到原型中的属性。</dd>
            </dl>
            <h2>6.2.3.1 理解原型对象</h2>
            <dl>
                <dt>关系说明</dt>
                <dd><img src="images/6.2/6.2.3_0.png" /></dd>
                <dd><img src="images/6.2/6.2.3_1.png" /></dd>
                <dd><img src="images/6.2/6.2.3_2.png" /></dd>
                <dd><img src="images/6.2/6.2.3_3.png" /></dd>
            </dl>
            <dl>
                <dt>检测是否为实例的原型：isPrototypeof</dt>
                <dd>Person.prototype.isPrototypeOf(person1)</dd>
                <dd>所有实现中都无法访问到实例对象的[[Prototype]]属性</dd>
            </dl>
            <dl>
                <dt>获取实例的原型：Object.getPrototypeOf()</dt>
                <dd>Object.getPrototypeOf(person1) === Person.prototype</dd>
                <dd>所有实现中都无法访问到实例对象的[[Prototype]]属性</dd>
            </dl>
            <dl>
                <dt>检测属性是否存在实例中：hasOwnProperty</dt>
                <dd>使用hasOwnProperty方法可以检测到一个属性是否存在于实例中，还是存在于原型中。</dd>
                <dd>该方法（不要忘了它是从Object继承来的），只在给定属性存在于对象实例的时候，才会返回true</dd>
            </dl>
            <ul>
                <li>构造函数由prototype属性指向原型对象</li>
                <li>实例出来的对象实例，有[[Prototype]]属性指向原型对象</li>
                <li>原型对象有constructor属性指向构造函数</li>
            </ul>
            <h2>6.2.3.2 原型与in操作符</h2>
            <dl>
                <dt>单独使用in</dt>
                <dd>只要属性可以访问，无论在实例还是原型，"name" in person1都为true</dd>
                <dd>同时使用in跟hasOwnProperty就可以确定该属性在实例中还是时原型对象中。</dd>
            </dl>
            <dl>
                <dt>for-in循环</dt>
                <dd>返回所有能够通过对象访问的，可以枚举的属性，其中既包括存在于实例中的属性，也包括存在于原型中的属性。</dd>
                <dd>屏蔽了原型中不可枚举属性的实例属性也会在for-in循环中返回。</dd>
            </dl>
            <dl>
                <dt>Object.keys</dt>
                <dd>要取得对象上所有可以枚举的实例属性。</dd>
                <dd>这里不包括原型对象的属性。</dd>
            </dl>
            <dl>
                <dt>Object.getOwnPropetyNames</dt>
                <dd>获取所有的实例属性，无论是否可以枚举。</dd>
            </dl>
            <h2>6.2.3.3 更简单的原型语法</h2>
            <dl>
                <dt>相关说明</dt>
                <dd>每创建一个函数，就同时会创建它的prototype对象，这个对象也会自动获得constructor属性。</dd>
                <dd>如果重写函数的prototype对象，那么constructor属性也就变成了新对象的constructor属性（指向Object构造函数），不再指向Person函数。</dd>
                <dd>此时，经instanceOf操作符还能返回正确的结果，但是通过constructor已经无法确定对象类型了。</dd>
                <dd>如果constructor的值非常重要，那么需要特意将它设置回适当的值。</dd>
            </dl>
            <h2>6.2.3.4 原型的动态性</h2>
            <dl>
                <dt>相关说明</dt>
                <dd>由于原型中查找值的过程时一次搜索，所以我们对原型对象所作的任何修改都能立即从实例上反映出来。即使先创建实例后修改原型也是如此。</dd>
                <dd>但如果重写整个原型，那么实例中的[[Prototype]]的指针跟新原型之间也就没有关系了。所以新原型的变化对实例没有影响。</dd>
                <dd><img src="images/6.2/6.2.4_1.png" /></dd>
            </dl>
            <h2>6.2.3.5 原生对象原型</h2>
            <dl>
                <dt>相关说明</dt>
                <dd>原型模式的重要性不仅体现在创建自定义类型方面，就连所有的原生引用类型，都是采用这种模式的创建的。</dd>
                <dd>所有原生引用类型（Object，Array,String,等等），都是在构造函数的原型上定义了方法。例如，在Array.prototype中可以找到sort方法，而在String.prototype中可以找到substring方法。
                </dd>
                <dd>通过原生对象的原型，不仅可以取得所有默认方法的引用，而且也可以定义新方法。可以像修改自定义对象的原型一样修改原生对象的原型，因此可以随意添加方法。</dd>
                <dd>尽管可以，但是我们不推荐在产品化的程序中修改原生对象的原型。其一，可能跟他人修改的原生方法冲突；其二，可能会重写原生方法。</dd>
                <dd><img src="images/6.2/6.2.5_1.png" /></dd>
                <dd><img src="images/6.2/6.2.5_2.png" /></dd>
            </dl>
            <h2>6.2.3.6 原型对象的问题</h2>
            <dl>
                <dt>相关说明</dt>
                <dd>优点：共享方法，避免所有的实例都定义相同的方法。</dd>
                <dd>缺点：共享属性值，对于基本值的属性影响不大，但是对于引用类型的属性值，不同实例共享引用类型属性值，会造成相互修改的问题。</dd>
            </dl>
            <div class="actions">
                <button onclick="handleCreatePrototypeExample()">简单示例</button>
                <button onclick="handleHasOwnPropertyExample()">检测属性是否存在实例中</button>
                <button onclick="handleInExample()">单独使用in操作符</button>
                <button onclick="handleObjectKeysExample()">Object.keys</button>
                <button onclick="handleSimplePrototypeExample()">更简单的原型语法</button>
                <button onclick="handleDynamicPrototypeExample()">原型的动态性</button>
                <button onclick="handleNativeObjectExample()">原生对象的类型</button>
                <button onclick="handlePrototypeQuestionExample()">原型对象的问题</button>
            </div>
        </section>
    </div>
    <div class="part">
        <h1>6.2.4 组合使用构造函数模式跟原型模式</h1>
        <section>
            <dl>
                <dt>相关说明</dt>
                <dd>创建自定义类型最常见的的方式就是组合使用构造函数模式跟原型模式。</dd>
                <dd>构造函数模式用于定义实例属性，而原型模式用于定义方法跟共享的属性。</dd>
                <dd>结果，每个实例都会有一份实例属性的副本，但同时又共享着对方法的引用，最大限度节省了内存。</dd>
                <dd>另外这种混合模式还支持向构造函数传递参数；可谓是集两种模式之长。</dd>
                <dd>这种构造函数与原型混成的模式，是目前ES中使用最广泛，认同度最高的一种创建自定义类型的方法。可以说，这是用来定义引用类型的一种默认模式。</dd>
            </dl>
            <div class="actions">
                <button onclick="handleConstructorAndPrototypeExample()">组合使用构造函数模式跟原型模式</button>
            </div>
        </section>
    </div>
    <div class="part">
        <h1>6.2.5 动态原型模式</h1>
        <section>
            <dl>
                <dt>相关说明</dt>
                <dd>实质上是对组合使用构造函数跟原型模式的一个改进，形式上合二为一。</dd>
                <dd>把所有的信息都封装到构造函数中，而通过在构造函数中初始化原型（仅在必要的情况下），又保持了同时使用构造函数和原型的优点。</dd>
                <dd>通过检查某个应该存在的方法是否有效，来决定是否需要初始化原型。</dd>
                <dd>if后面的代码，只会在初次调用构造函数的时候才会执行。</dd>
                <dd>不必用一大堆if来检查每个属性跟方法，只需要检查其中一个便可。</dd>
            </dl>
            <div class="actions">
                <button onclick="handleDynamicPrototypeExample()">动态原型模式</button>
            </div>
        </section>
    </div>
    <div class="part">
        <h1>6.2.6 寄生构造函数模式</h1>
        <section>
            <dl>
                <dt>相关说明</dt>
                <dd>形似构造函数模式但又不是。</dd>
                <dd>披着构造函数外衣的工厂模式。</dd>
                <dd>该模式在特殊情况下可以用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组，由于不能直接修改Array构造函数，因此可以使用该模式。</dd>
                <dd>该模式返回的对象与构造函数或者构造函数的原型属性没有关系，不能依赖instanceof操作符来确定对象类型。</dd>
            </dl>
            <div class="actions">
                <button onclick="handleParasiticExample()">动态原型模式</button>
            </div>
        </section>
    </div>
    <div class="part">
        <h1>6.2.7 稳妥构造函数模式</h1>
        <section>
            <dl>
                <dt>相关说明</dt>
                <dd>寄生构造函数模式的升级版。</dd>
                <dd>所谓稳妥对象，指的是没有公共属性，而且其方法也不引用this的对象。</dd>
                <dd>最适合在一些安全环境中（这些环境禁止使用this和new），或者防止数据被其他应用恒旭改动时使用。</dd>
                <dd>与寄生构造函数模式区别：一是新创建对象的实例方法不引用this；二是不使用new操作符调用构造函数。</dd>
                <dd>变量friend中保存的是一个稳妥对象，而除了调用sayName()方法外，没有别的方法可以访问到其数据成员。</dd>
                <dd>即使有其他代码会给这个对象添加方法跟数据成员，但也不可能有别的办法访问传入到构造函数中的原始数据。</dd>
                <dd>考虑类似于用户名跟密码类似的敏感信息，防止其他地方访问到。</dd>
            </dl>
            <div class="actions">
                <button onclick="handleDurableObjectExample()">动态原型模式</button>
            </div>
        </section>
    </div>
    <script>
        "use strict";
        // =============6.2.1 工厂模式
        function handleCreateObjectFactoryPattern() {
            function createPerson(name, age, job) {
                var o = new Object();
                o.name = name;
                o.age = age;
                o.job = job;
                o.sayName = function () {
                    console.warn("name:", this.name)
                }
                return o
            }
            var person1 = createPerson("lee", 29, "software engineer")
            var person2 = createPerson("jack", 27, "doctor")
            console.warn("object", person1, person2)
            person1.sayName()
            person2.sayName()
        }
        // =============6.2.2 构造函数模式
        function handleCreateObjectConstructorPattern() {
            function Person(name, age, job) {
                this.name = name;
                this.age = age;
                this.job = job;
                this.sayName = function () {
                    console.warn("name:", this.name)
                }
            }
            var person1 = new Person("lee", 29, "software engineer")
            var person2 = new Person("jack", 27, "doctor")
            console.warn("object", person1, person2)
            person1.sayName()
            person2.sayName()
            console.warn("constructor", person1.constructor === Person, person2.constructor === Person)
            console.warn("instanceof", person1 instanceof Person, person1 instanceof Object)
            // 当构造函数使用
            var person3 = new Person("tom", 28, "teacher")
            person3.sayName()
            // 作为普通函数调用
            Person("frank", 21, "policeman") // 添加到window上（严格模式下报错）
            window.sayName()
            // 在另一个对象作用域里调用（严格模式下报错）
            var o = new Object()
            Person.call(o, "jane", 22, "actor")
            o.sayName()
        }
        // ==========================================6.2.3 原型模式
        /**
         * handleCreatePrototypeExample
         */
        function handleCreatePrototypeExample() {
            function Person() { }
            Person.prototype.name = "lee"
            Person.prototype.age = 29
            Person.prototype.job = "teacher"
            Person.prototype.sayName = function () {
                console.warn("name:", this.name)
            }
            var person1 = new Person()
            person1.sayName()
            var person2 = new Person()
            person2.sayName()
            console.warn("person name:", person1.name, person2.name)
            person1.name = "jack"
            console.warn("person name:", person1.name, person2.name)
            delete person1.name
            console.warn("person name:", person1.name, person2.name)
        }
        /**
         * handleHasOwnPropertyExample
         */
        function handleHasOwnPropertyExample() {
            function Person() { }
            Person.prototype.name = "lee"
            Person.prototype.age = 29
            Person.prototype.job = "teacher"
            Person.prototype.sayName = function () {
                console.warn("name:", this.name)
            }
            var person1 = new Person()
            var person2 = new Person()
            console.warn("person1.hasOwnProperty(\"name\")", person1.hasOwnProperty("name"))
            person1.name = "jack"
            console.warn("person1.name", person1.name)
            console.warn("person1.hasOwnProperty(\"name\")", person1.hasOwnProperty("name"))
            console.warn("person2.name", person2.name)
            console.warn("person2.hasOwnProperty(\"name\")", person2.hasOwnProperty("name"))
            delete person1.name
            console.warn("person1.name", person1.name)
            console.warn("person1.hasOwnProperty(\"name\")", person1.hasOwnProperty("name"))
            console.warn("person1", person1)
            console.warn("Person", Person, Person.prototype)
            // 确定是否为原型对象的实例
            console.warn("Person.prototype.isPrototypeOf(person1)", Person.prototype.isPrototypeOf(person1))
            console.warn("Object.getPrototypeOf(person1)===Person.prototype", Object.getPrototypeOf(person1) === Person.prototype)
            console.warn("Object.getPrototypeOf(person1)", Object.getPrototypeOf(person1))
        }
        /**
         * handleInExample
         */
        function handleInExample() {
            function Person() { }
            Person.prototype.name = "lee"
            Person.prototype.age = 29
            Person.prototype.job = "teacher"
            Person.prototype.sayName = function () {
                console.warn("name:", this.name)
            }
            var person1 = new Person()
            var person2 = new Person()
            console.warn("person1.hasOwnProperty(\"name\")", person1.hasOwnProperty("name"))
            console.warn("name in person1", "name" in person1)

            person1.name = "jack"
            console.warn("person1.name", person1.name)
            console.warn("person1.hasOwnProperty(\"name\")", person1.hasOwnProperty("name"))
            console.warn("name in person1", "name" in person1)

            console.warn("person2.name", person2.name)
            console.warn("person2.hasOwnProperty(\"name\")", person2.hasOwnProperty("name"))
            console.warn("name in person2", "name" in person2)

            delete person1.name
            console.warn("person1.name", person1.name)
            console.warn("person1.hasOwnProperty(\"name\")", person1.hasOwnProperty("name"))
            console.warn("name in person1", "name" in person1)
        }
        /**
         * handleObjectKeysExample
         */
        function handleObjectKeysExample() {
            function Person() { }
            Person.prototype.name = "lee"
            Person.prototype.age = 29
            Person.prototype.job = "teacher"
            Person.prototype.sayName = function () {
                console.warn("name:", this.name)
            }
            var person1 = new Person()
            console.warn("Object.keys(person1)", Object.keys(person1))
            console.warn("Object.keys(Person.prototype)", Object.keys(Person.prototype))
            console.warn("Object.keys(Object.getPrototypeOf(person1))", Object.keys(Object.getPrototypeOf(person1)))
            console.warn("Object.getOwnPropertyNames(person1)", Object.getOwnPropertyNames(person1))
            console.warn("Object.getOwnPropertyNames(Person.prototype)", Object.getOwnPropertyNames(Person.prototype))
        }
        /**
         * handleSimplePrototypeExample
         */
        function handleSimplePrototypeExample() {
            function Person() { }
            Person.prototype = {
                // constructor: Person, // 重新设置回该属性，这种方式回导致其[[Enumberable]]为true
                name: "lee",
                age: 29,
                job: "teacher",
                sayName: function () {
                    console.warn("name:", this.name)
                }
            }
            // 以下这个方式不会导致[[Enumberable]]为true
            Object.defineProperty(Person.prototype, "constructor", {
                enumerable: false,
                value: Person,
            })
            var friend = new Person();
            console.warn("friend instanceof Object", friend instanceof Object)
            console.warn("friend instanceof Person", friend instanceof Person)
            console.warn("friend.constructor === Object", friend.constructor === Object)
            console.warn("friend.constructor === Person", friend.constructor === Person)
        }
        /**
         * handleDynamicPrototypeExample 
         */
        function handleDynamicPrototypeExample() {
            function Person() { }
            var friend = new Person();
            Person.prototype.sayHi = function () {
                console.warn("sayHi")
            }
            friend.sayHi()
            Person.prototype = {
                constructor: Person,
                name: "lee",
                age: 29,
                job: "teacher",
                sayName: function () {
                    console.warn("name:", this.name)
                }
            }
            friend.sayName()
        }
        /**
         * handleNativeObjectExample
         */
        function handleNativeObjectExample() {
            console.warn("Array.prototype", Array.prototype)
            console.warn("String.prototype", String.prototype)

            String.prototype.startsWith2 = function (text) {
                console.warn("this is startsWith2")
                return this.indexOf(text) === 0
            }
            console.warn("String.prototype", String.prototype)
            var msg = "Hello world!"
            console.warn("String.prototype", msg.startsWith2("Hello"))
        }
        /**
         * handlePrototypeQuestionExample
         */
        function handlePrototypeQuestionExample() {
            function Person() { }
            Person.prototype = {
                constructor: Person,
                name: "lee",
                age: 29,
                job: "teacher",
                friends: ["tom", "jane"],
                sayName: function () {
                    console.warn("name:", this.name)
                }
            }
            var person1 = new Person()
            var person2 = new Person()
            person1.friends.push("Lilei")
            console.warn("person1.friends", person1.friends)
            console.warn("person2.friends", person2.friends)
            console.warn("person1.friends === person2.friends", person1.friends === person2.friends)
        }
        // =======================================6.2.4组合使用构造函数模式跟原型模式
        /**
         * handleConstructorAndPrototypeExample
         */
        function handleConstructorAndPrototypeExample() {
            function Person(name, age, job) {
                this.name = name
                this.age = age
                this.job = job
                this.friends = ["tom", "jane"]
            }
            Person.prototype = {
                constructor: Person,
                sayName: function () {
                    console.warn("name", this.name)
                }
            }
            var person1 = new Person("lee", 29, "teacher")
            var person2 = new Person("jone", 21, "student")

            person1.friends.push("Lilei")
            console.warn("person1.friends", person1.friends)
            console.warn("person2.friends", person2.friends)
            console.warn("person1.friends === person2.friends", person1.friends === person2.friends)
            console.warn("person1.sayName ===person2.sayName", person1.sayName === person2.sayName)
        }
        // ================================================6.2.5 动态原型模式
        /**
         * handleDynamicPrototypeExample
         */
        function handleDynamicPrototypeExample() {
            function Person(name, age, job) {
                this.name = name
                this.age = age
                this.job = job
                // 方法
                if (typeof this.sayName !== "function") {
                    Person.prototype.sayName = function () {
                        console.warn("name", this.name)
                    }
                }
            }
            var friend = new Person("lee", 29, "teacher")
            friend.sayName()
        }
        // ===========================================6.2.6 寄生构造函数模式
        /**
         * handleParasiticExample
         */
        function handleParasiticExample() {
            function Person(name, age, job) {
                var o = new Object();
                o.name = name
                o.age = age
                o.job = job
                o.sayName = function () {
                    console.warn("name", this.name)
                }
                return o
            }
            var friend = new Person("lee", 29, "teacher")
            friend.sayName()
            // apply
            function SpecialArray() {
                // 创建数组
                var values = new Array();
                // 添加值
                values.push.apply(values, arguments)
                // 添加方法
                values.toPipedString = function () {
                    return this.join("|")
                }
                // 返回数组
                return values
            }
            var colors = new SpecialArray("red", "green", "yello")
            console.warn("colors.toPipedString()", colors.toPipedString())
        }
        // ===============================6.2.7 稳妥构造函数模式
        /**
         * handleDurableObjectExample
         */
        function handleDurableObjectExample() {
            function Person(name, age, job) {
                // 创建要返回的对象
                var o = new Object();
                // 可以在这里定义私有变量跟函数

                // 添加方法
                o.sayName = function () {
                    console.warn("name", name)
                }
                // 返回对象
                return o
            }
            var friend = Person("lee", 29, "teacher")
            friend.sayName()
        }
    </script>
</body>

</html>